/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments.relations.delete;

import java.util.Date;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.data.context.DeleteRelationRequestContext;
import org.unidata.mdm.data.dto.DeleteRelationDTO;
import org.unidata.mdm.data.exception.DataExceptionIds;
import org.unidata.mdm.data.exception.DataProcessingException;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.DataRecordsService;
import org.unidata.mdm.data.service.impl.CommonRelationsComponent;
import org.unidata.mdm.data.service.impl.RelationDraftProviderComponent;
import org.unidata.mdm.data.service.segments.ContainmentRelationSupport;
import org.unidata.mdm.data.type.apply.RelationDeleteChangeSet;
import org.unidata.mdm.data.type.data.OriginRelation;
import org.unidata.mdm.data.type.data.RelationType;
import org.unidata.mdm.data.type.keys.RelationKeys;
import org.unidata.mdm.system.type.pipeline.Start;

/**
 * @author Mikhail Mikhailov on Nov 24, 2019
 */
@Component(RelationDeleteStartExecutor.SEGMENT_ID)
public class RelationDeleteStartExecutor extends Start<DeleteRelationRequestContext, DeleteRelationDTO> implements ContainmentRelationSupport {
    /**
     * The logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(RelationDeleteStartExecutor.class);
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RELATION_DELETE_START]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".relation.delete.start.description";
    /**
     * Common rel component.
     */
    @Autowired
    private CommonRelationsComponent commonRelationsComponent;
    /**
     * The DRS.
     */
    @Autowired
    private DataRecordsService dataRecordsService;
    /**
     * RDPC.
     */
    @Autowired
    private RelationDraftProviderComponent relationDraftProviderComponent;
    /**
     * Constructor.
     */
    public RelationDeleteStartExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION, DeleteRelationRequestContext.class, DeleteRelationDTO.class);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void start(DeleteRelationRequestContext ctx) {
        setup(ctx);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String subject(DeleteRelationRequestContext ctx) {
        setup(ctx);
        RelationKeys keys = ctx.relationKeys();
        return keys.getRelationName();
    }

    /**
     * {@inheritDoc}
     */
    protected void setup(DeleteRelationRequestContext ctx) {

        if (ctx.setUp()) {
            return;
        }

        // 1. Setup timeline and check keys
        setupTimeline(ctx);

        // 2. Check state.
        setupCheck(ctx);

        // 3. Possibly setup change set
        setupFields(ctx);

        // 4. Containment
        setupContainment(ctx);

        ctx.setUp(true);
    }

    protected void setupCheck(DeleteRelationRequestContext ctx) {

        RelationKeys relationKeys = ctx.relationKeys();

        checkRecordPresence(ctx, relationKeys);

        checkFlagsDefined(ctx);

        checkMultipleFlags(ctx);

        checkDraftFlags(ctx);

        checkRelationState(ctx, relationKeys);
    }

    protected void setupTimeline(DeleteRelationRequestContext ctx) {

        Timeline<OriginRelation> timeline = commonRelationsComponent.ensureAndGetRelationTimeline(ctx);
        RelationKeys relationKeys = timeline != null ? timeline.getKeys() : null;

        ctx.currentTimeline(timeline);
        ctx.relationKeys(relationKeys);
    }

    protected void setupFields(DeleteRelationRequestContext ctx) {

        RelationKeys relationKeys = ctx.relationKeys();

        // May be already set by batch
        if (Objects.isNull(ctx.changeSet())) {
            RelationDeleteChangeSet set = new RelationDeleteChangeSet();
            set.setRelationType(ctx.relationType());
            ctx.changeSet(set);
        }

        Date ts = new Date(System.currentTimeMillis());

        ctx.relationName(relationKeys.getRelationName());
        ctx.relationType(relationKeys.getRelationType());
        ctx.relationKeys(relationKeys);
        ctx.timestamp(ts);
    }

    protected void setupContainment(DeleteRelationRequestContext ctx) {

        if (ctx.relationType() != RelationType.CONTAINS) {
            return;
        }

        delete(ctx);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationDraftProviderComponent draftProviderComponent() {
        return relationDraftProviderComponent;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public DataRecordsService dataRecordsService() {
        return dataRecordsService;
    }

    private void checkRecordPresence(DeleteRelationRequestContext ctx, RelationKeys relationKeys) {

        if (Objects.isNull(relationKeys)) {

            final String relationName = ctx.relationName();
            final String message
                = "Relation delete: relation of type [{}] not found by supplied keys - relation etalon id [{}], relation origin id [{}], "
                + "etalon id: [{}], origin id [{}], external id [{}], source system [{}], name [{}]";
            LOGGER.warn(message,
                    relationName,
                    ctx.getRelationEtalonKey(),
                    ctx.getRelationOriginKey(),
                    ctx.getEtalonKey(),
                    ctx.getOriginKey(),
                    ctx.getExternalId(),
                    ctx.getSourceSystem(),
                    ctx.getEntityName());
            throw new DataProcessingException(message, DataExceptionIds.EX_DATA_RELATIONS_DELETE_NOT_FOUND,
                    relationName,
                    ctx.getRelationEtalonKey(),
                    ctx.getRelationOriginKey(),
                    ctx.getEtalonKey(),
                    ctx.getOriginKey(),
                    ctx.getExternalId(),
                    ctx.getSourceSystem(),
                    ctx.getEntityName());
        }
    }
    private void checkFlagsDefined(DeleteRelationRequestContext ctx) {

        if (!ctx.isInactivateEtalon()
         && !ctx.isInactivateOrigin()
         && !ctx.isInactivatePeriod()
         && !ctx.isWipe()) {
            throw new DataProcessingException("No action was given to relation DELETE. "
                   + "One of 'inactivateEtalon', 'inactivateOrigin', 'inactivatePeriod' or 'wipe' must be defined.",
                  DataExceptionIds.EX_DATA_RELATIONS_DELETE_ACTION_NOT_DEFINED);
        }
    }
    private void checkMultipleFlags(DeleteRelationRequestContext ctx) {

        if ((ctx.isWipe() && (ctx.isInactivateEtalon() || ctx.isInactivatePeriod() || ctx.isInactivateOrigin()))
         || (ctx.isInactivateEtalon() && (ctx.isWipe() || ctx.isInactivatePeriod() || ctx.isInactivateOrigin()))
         || (ctx.isInactivatePeriod() && (ctx.isInactivateEtalon() || ctx.isWipe() || ctx.isInactivateOrigin()))
         || (ctx.isInactivateOrigin() && (ctx.isInactivateEtalon() || ctx.isInactivatePeriod() || ctx.isWipe()))) {
           throw new DataProcessingException("More than one action was given to relation DELETE. "
                   + "Only one of 'inactivateEtalon', 'inactivateOrigin', 'inactivatePeriod' or 'wipe' must be defined.",
                   DataExceptionIds.EX_DATA_RELATIONS_DELETE_MORE_THAN_ONE_ACTION_DEFINED);
        }
    }
    private void checkDraftFlags(DeleteRelationRequestContext ctx) {

        if (ctx.isDraftOperation() && ctx.isWipe()) {
            throw new DataProcessingException("Invalid input relation DELETE input combination. Wipe operation cannot be handled as draft.",
                   DataExceptionIds.EX_DATA_RELATIONS_DELETE_WIPE_AND_DRAFT_COMBINATION);
        }
    }
    private void checkRelationState(DeleteRelationRequestContext ctx, RelationKeys relationKeys) {

        if (ctx.isInactivateEtalon() && !relationKeys.isActive()) {
            final String message = "Relation [{}], etalon id [{}] is already in inactive state.";
            LOGGER.warn(message, relationKeys.getRelationName(), relationKeys.getEtalonKey().getId());
            throw new DataProcessingException(message, DataExceptionIds.EX_DATA_RELATIONS_DELETE_ALREADY_INACTIVE,
                    relationKeys.getRelationName(), relationKeys.getEtalonKey().getId());
        }
    }
}
